Skip to content

Conversation

@alexbartok
Copy link
Contributor

@alexbartok alexbartok commented Dec 29, 2025

Sorry, one more while I'm at it ;-)


Add Launch at Login Functionality

Summary

Adds the ability to automatically start BrewServicesManager when the user logs in to macOS. This feature uses the modern SMAppService API from the ServiceManagement framework, providing a native and reliable launch-at-login experience.

Changes

New Files

  • LaunchAtLoginError.swift - Error type for handling launch-at-login failures with localized descriptions and recovery suggestions
  • BrewServicesManager.entitlements - Entitlements file for code signing with documentation explaining why app sandbox is disabled to preserve Homebrew access

Modified Files

  • AppSettings.swift - Added launch-at-login state management with:
    • UserDefaults persistence for the setting
    • System state synchronization on app launch (optimized to prevent redundant API calls)
    • Automatic registration/unregistration with SMAppService
    • Graceful error handling with state reversion on failure
  • SettingsView.swift - Added new "Launch" settings card with:
    • Toggle control for enabling/disabling launch at login
    • Inline error display with recovery suggestions
    • Direct link to System Settings when user approval is needed
  • project.pbxproj - Configured CODE_SIGN_ENTITLEMENTS for both Debug and Release builds
  • project.pbxproj - Updated deployment target to 15.0 (from 15.7) and configured CODE_SIGN_ENTITLEMENTS for both Debug and Release builds
  • README.md - Updated feature list and settings documentation

Implementation Details

Architecture

The implementation follows existing patterns in the codebase:

  • Integrated directly into AppSettings (@observable class) rather than creating a separate manager
  • Uses UserDefaults for persistence with didSet observers for automatic updates
  • Errors are stored as optional properties and displayed inline in the UI

Key Features

  • State Synchronization: Automatically syncs with macOS login items on app launch to handle manual changes made through System Settings. Uses a synchronization flag to prevent redundant system API calls during state updates.
  • Error Handling: Non-blocking errors with user-friendly messages and recovery suggestions
  • No Sandboxing Required: Uses minimal entitlements (no app sandbox) to preserve full Homebrew access while still supporting ServiceManagement

Technical Decisions

Why No App Sandbox?

Initially implemented with full app sandboxing (as commonly recommended for ServiceManagement), but this broke Homebrew detection and command execution. Testing revealed that SMAppService works perfectly without app sandboxing on modern macOS. The entitlements file includes only the minimal com.apple.application-identifier key, with XML comments documenting why sandboxing is disabled (to preserve shell access for Homebrew commands, sudo privilege escalation, and lsof port detection).

Error State Handling

.notFound status (which occurs when running from Xcode or when not yet registered) is treated as a normal state rather than an error, preventing unnecessary error messages on first launch.

Testing

Manual Testing Checklist

  • Toggle "Launch at login" ON → verify app appears in System Settings > General > Login Items
  • Toggle OFF → verify app is removed from Login Items
  • Quit and relaunch → verify setting persists correctly
  • Log out and back in with setting enabled → verify app launches automatically
  • Manually remove from System Settings → relaunch app → verify toggle syncs to OFF
  • Verify Homebrew functionality still works (not broken by entitlements)
  • Build succeeds for both Debug and Release configurations

Notes

  • The app must be properly code-signed for this feature to work
  • Users may see a macOS permission prompt on first toggle (this is expected behavior)
  • If approval is required, the UI provides a direct link to System Settings

alexbartok and others added 12 commits December 28, 2025 14:58
Add ability to detect and display listening ports for running services using lsof.

Features:
- Detect TCP/UDP listening ports for services and their child processes
- Display ports in service info panel with protocol and port number
- Show port summary in service actions popover (first 3 ports)
- Automatically detect child processes to catch ports from worker processes

Implementation:
- PortDetector actor uses lsof to find listening ports by PID
- Recursively finds all descendant PIDs to include child process ports
- ServicePort model represents detected ports with protocol type
- Ports displayed in both detail panel and popover menu

Technical details:
- Uses `lsof -nP -iTCP -sTCP:LISTEN -a -p {pids}` for detection
- Uses `ps -o pid= -g {pid}` to find child processes
- Port detection is non-blocking and gracefully handles errors
- No thousands separators in port number display

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Add ability to configure custom URLs for each service with auto-detection based on detected ports.

Features:
- Configure custom URLs per service (e.g., http://localhost:8384 for Syncthing)
- Auto-suggest URLs based on detected listening ports
- Display links as inline icon buttons in service rows (up to 2 visible)
- Full link management UI with add/edit/delete functionality
- Persistent storage across app restarts
- Service-agnostic design (no hardcoded service-specific URLs)

Implementation:
- ServiceLinksStore manages links with JSON persistence to Application Support
- Links displayed inline in service rows and in popover menu
- ServiceLinksManagementView provides full CRUD interface
- Smart URL suggestions for common HTTP/HTTPS ports
- Opens links in default browser via NSWorkspace

Technical details:
- Uses @observable pattern for reactive state management
- Persists to ~/Library/Application Support/BrewServicesManager/{bundleId}/service-links.json
- Inline overlay-based forms (avoids .sheet() issues in menu bar extras)
- Supports multiple links per service with optional custom labels
- Auto-focuses text fields for better UX

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Improve visual consistency in the popover header section:
- Use consistent spacing for all HStack elements in header
- Align status and port information properly with action icons
- Match line heights between header section and menu items

Changes:
- Change VStack spacing from tightSpacing to compactSpacing for better vertical rhythm
- Add explicit tightSpacing to status and operation HStacks
- Ensures network icon and status icon align consistently

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Fix system authentication dialog interaction issue in MenuBarExtra context.

Problem:
When switching to system domain or performing privileged operations, the
osascript authentication dialog appears but cannot receive mouse clicks
because the MenuBarExtra interferes with input handling.

Solution:
Call NSApp.activate(ignoringOtherApps: true) before showing the authentication
dialog. This brings the app to the foreground and ensures the system dialog
can properly receive user input.

Changes:
- Import AppKit in PrivilegeEscalator
- Add NSApp.activate() call on MainActor before executing osascript
- Dialog now properly accepts clicks and keyboard input

This complements the earlier fix for SwiftUI dialogs (using inline overlays
instead of .sheet()) to fully resolve dialog interaction issues in menu bar
extras.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Replaces command-line osascript with NSAppleScript API running on MainActor.
This fixes the issue where the system authentication dialog could not be
interacted with using the mouse in MenuBarExtra apps.

The previous approach of activating the app didn't work because osascript
ran as a separate process, causing focus issues. NSAppleScript executes
in-process on the main thread, properly associating the auth dialog with
the app's event loop.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Adds comprehensive documentation for the new features:
- Port detection: automatic discovery of listening ports
- Service links: user-configurable URLs for web interfaces
- Troubleshooting section for port detection issues
- Documentation for the authentication dialog fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Removes hardcoded DEVELOPMENT_TEAM settings to allow contributors to use
their own Apple Developer accounts. Contributors will need to select their
team in Xcode's Signing & Capabilities editor when building.

This is standard practice for open source macOS projects.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Adds clear instructions for configuring code signing after removing
hardcoded development team IDs. Documents the requirement for an Apple
Developer account and provides step-by-step setup instructions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Fix data race in ServiceLinksStore save() method
- Fix child process detection using pgrep instead of ps -g
- Improve URL validation with blacklist approach
- Simplify redundant port range logic
- Make detectedPorts immutable in BrewServiceInfoEntry
- Fix misleading PostgreSQL example in README
- Remove thousands separator from port suggestions

Addresses feedback from Copilot PR review on:
- Data race concerns with Swift concurrency
- Incorrect ps command semantics
- Security concerns with URL validation
- Code quality improvements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Remove thousands separators from port numbers in UI
- Trigger port detection when actions popover opens
- Ensure ports appear immediately without requiring View Info click

Port numbers now display without locale formatting (8080 not 8,080)
and are detected eagerly when user opens the actions menu.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
@validatedev
Copy link
Owner

You're amazing thank you! I'll check this PR tomorrow

@validatedev
Copy link
Owner

@alexbartok could you please resolve the conflict? so I'll review after that

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds launch-at-login functionality using the modern SMAppService API from ServiceManagement, along with two additional features: automatic port detection for running services and user-configurable service links for quick access to web interfaces.

Key Changes:

  • Launch-at-login support with UserDefaults persistence and system state synchronization
  • Port detection using lsof to identify listening ports for services and their child processes
  • Service links management for storing and accessing custom URLs per service

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
LaunchAtLoginError.swift New error type for launch-at-login failures with localized descriptions
BrewServicesManager.entitlements Minimal entitlements configuration (no app sandbox to preserve Homebrew access)
AppSettings.swift Launch-at-login state management with UserDefaults and SMAppService integration
SettingsView.swift UI for launch-at-login toggle with inline error display
AppKitBridge.swift Added URL opening helper method for consistency
project.pbxproj Code signing configuration and development team setup
README.md Updated documentation for new features and build requirements
ServicePort.swift New model for representing listening ports with protocol information
PortDetector.swift Port detection using lsof with child process support
ServiceLinksStore.swift Persistent storage for user-configured service links
ServiceLink.swift Model for user-configured service URLs
ServiceLinksManagementView.swift UI for managing service links with validation
ServicesStore.swift Added port detection integration
BrewServiceInfoEntry.swift Added runtime-only detectedPorts property
ServiceInfoView.swift Display detected ports in service info
ServiceActionsPopoverView.swift Show port summary and links in popover
ServiceMenuItemView.swift Display service links inline
MenuBarRootView.swift Added links management overlay
MainMenuServicesSectionView.swift Wired up links management callback
MainMenuContentView.swift Passed through links management handler
BrewServicesManagerApp.swift Added ServiceLinksStore to environment
PrivilegeEscalator.swift Switched to NSAppleScript for better dialog control

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

alexbartok and others added 4 commits January 4, 2026 22:49
Removes hardcoded DEVELOPMENT_TEAM from project.pbxproj and uses
gitignored xcconfig files instead. This keeps the repo clean while
allowing contributors to easily configure their own team ID.

Contributors now copy Development.shared.xcconfig to Development.xcconfig
and set their team ID there.

Also removes 'file' from blockedSchemes to allow legitimate file:// URLs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Adds the ability to automatically start the app when the user logs in
to macOS, using the modern SMAppService API from ServiceManagement
framework.

Changes:
- Add LaunchAtLoginError.swift for error handling
- Extend AppSettings with launch-at-login state management and system
  synchronization
- Update SettingsView with toggle UI and inline error display
- Add entitlements file (without app sandbox to preserve Homebrew access)
- Configure CODE_SIGN_ENTITLEMENTS in Xcode project
- Update README with new feature documentation

The implementation:
- Syncs with system state on app launch to handle manual changes
- Handles errors gracefully with user-friendly messages
- Provides direct link to System Settings when approval is needed
- Works without requiring full app sandboxing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Remove didSet trigger in AppSettings to prevent redundant API calls
- Remove 'file' from blockedSchemes to allow legitimate file:// URLs
- Implement xcconfig-based team configuration for clean repo

Addresses review comments from GitHub Copilot PR review.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Remove didSet triggers in syncLaunchAtLoginState to prevent infinite loops
- Use AppKitBridge.openURL for consistency instead of NSWorkspace

Addresses additional review comments from GitHub Copilot.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
@alexbartok alexbartok force-pushed the feature/launch-at-login branch from b0c31a4 to ab9e4c1 Compare January 4, 2026 22:01
@validatedev
Copy link
Owner

Thanks for the fixes, but the PR still has merge conflicts. Could you have a chance to resolve these also?

Resolves merge conflicts by:
- Accepting upstream's refactored architecture (enum-based routing, view separation)
- Accepting upstream's improved port detection and service links implementation
- Accepting upstream's Sparkle auto-update integration (v1.1.3)
- Preserving launch-at-login functionality with ServiceManagement
- Preserving xcconfig-based team configuration
- Combining Settings UI for both updates and launch-at-login
- Adopting upstream's actor isolation improvements (nonisolated annotations)
- Using continuation pattern for privilege escalation dialog handling

Key changes:
- BrewServicesManagerApp: Restored AppUpdater state and badge logic
- AppSettings: Merged automaticallyCheckForUpdates + launchAtLogin properties
- SettingsView: Combined Launch, Updates, and About sections
- PrivilegeEscalator: nonisolated enum with withCheckedThrowingContinuation pattern
- AppKitBridge: Removed @mainactor, added openURL method
- project.pbxproj: Added xcconfig, removed hardcoded DEVELOPMENT_TEAM
- README: Updated with xcconfig setup instructions

Version: 1.1.3 (build 7)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
@alexbartok
Copy link
Contributor Author

Here you go!

@validatedev
Copy link
Owner

LGTM!

@validatedev validatedev merged commit 3b9c6b7 into validatedev:main Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants